package distributed_block

import (
	"context"
	"fmt"
	"github.com/go-errors/errors"
	"github.com/redis/go-redis/v9"
	"github.com/zeromicro/go-zero/core/logc"
	"payme3000/framework/errorx"
	"time"
)

const (
	KeyPrefix = "dist:bk"
)

type (
	DistributedBlock struct {
		name   string
		expiry time.Duration

		client redis.UniversalClient // 缓存驱动
	}

	UnLockFunc func(context.Context)
	OptionFunc func(*DistributedBlock)
)

func NewDistributedBlock(
	name string,
	client redis.UniversalClient,
	options ...OptionFunc,
) *DistributedBlock {
	m := &DistributedBlock{
		name:   name,
		expiry: 8 * time.Second,
		client: client,
	}

	for _, opt := range options {
		opt(m)
	}

	return m
}

// LockWithKey 锁定指定key
// @param ctx context.Context 用于退出持续尝试获取锁的等待
// @return UnLockFunc 调用后执行对应key的unlock
func (m *DistributedBlock) LockWithKey(ctx context.Context, key string) (UnLockFunc, bool, error) {
	key = m.GetKey(key)
	value := time.Now().Unix()

	ok, err := m.client.SetNX(ctx, key, value, m.expiry).Result()
	if err != nil {
		if errors.Is(err, context.DeadlineExceeded) {
			logc.Infof(ctx, "early exit to acquire lock with err:%s, key:%s", err, key)
			return nil, false, err
		}

		return nil, false, errorx.Throw(ctx, err)
	}

	// lock失败
	if !ok {
		return nil, ok, nil
	}

	// lock 成功 //

	// 设置版本号
	_ver := value
	return func(unlockCtx context.Context) {
		if _ver == value {
			if delErr := m.client.Del(unlockCtx, key).Err(); delErr != nil {
				_ = errorx.Errorf(unlockCtx, "unlock error with err:%s, key:%s", delErr, key)
			}

			_ver++
		}
	}, true, nil
}

func (m *DistributedBlock) GetKey(key string) string {
	return fmt.Sprintf("%s:%s:%s", KeyPrefix, m.name, key)
}
